【Unity】【エディタ拡張】ノードエディタを作るGraph Viewの基本的な使い方

您所在的位置:网站首页 unity graph view 【Unity】【エディタ拡張】ノードエディタを作るGraph Viewの基本的な使い方

【Unity】【エディタ拡張】ノードエディタを作るGraph Viewの基本的な使い方

2024-07-07 00:56| 来源: 网络整理| 查看: 265

2020-06-16 【Unity】【エディタ拡張】ノードエディタを作るGraph Viewの基本的な使い方 Unity UnityEditor

Unityでノードエディタを簡単に作れるGraph Viewの基本的な使い方についてまとめました。

作るもの ノードを作る グラフを作る ウィンドウに表示する 他のノードを作る ノードを右クリックメニューから作れるようにする ノード同士を繋げられるようにする 関連

Unity2019.4.0

作るもの

UnityのShader GraphやVFX Graphを使うと、以下のようにノードベースのエディタによりアセットが編集できます。

f:id:halya_11:20200613220643p:plainShader Graph

Unityが用意しているGraphViewとその周辺クラスを使うとこのようなノードエディタのViewを簡単に作ることができます。 GraphViewは現時点でExperimentalという位置付けで公開されていますが、 既にShader Graphなどに使われていることもあり普通に使えるのではないかと思います。

本記事ではこの機能を使って以下のような簡単なグラフを作ってみます。

f:id:halya_11:20200613220455p:plain最終成果物

なおGraph ViewはUIElementsを使って作られています。 これに関しては以下の記事にまとめていますので必要に応じて参照してください。

light11.hatenadiary.com

ノードを作る

まず以下のようなノードを表すクラスを作ります。

f:id:halya_11:20200613221720p:plainノード

ノードはNodeクラスを継承することにより作成します。 また、ノードには入出力ポートが存在するのでコンストラクタでそれらを作成します。 今回は入力ポートと出力ポートを一つずつ作成しました。

using UnityEditor.Experimental.GraphView; public class ExampleNode : Node { public ExampleNode() { title = "Example"; // 入力用のポートを作成 var inputPort = Port.Create(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(float)); // 第三引数をPort.Capacity.Multipleにすると複数のポートへの接続が可能になる inputPort.portName = "Input"; inputContainer.Add(inputPort); // 入力用ポートはinputContainerに追加する // 出力用のポートを作る var outputPort = Port.Create(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, typeof(float)); outputPort.portName = "Value"; outputContainer.Add(outputPort); // 出力用ポートはoutputContainerに追加する } } グラフを作る

次にこのポートを表示するためのグラフを作ります。

グラフはGraphViewを継承したクラスを作ることで作成できます。 とりあえず確認用に前節で作ったノードを一つAddElement()で子に追加しています。

またついでにサイズを親に合わせたり基本的なマウス操作ができるようにセットアップしています。

using UnityEditor; using UnityEngine.UIElements; using UnityEditor.Experimental.GraphView; public class ExampleGraphView : GraphView { public ExampleGraphView(EditorWindow editorWindow) { // ノードを追加 AddElement(new ExampleNode()); // 親のサイズに合わせてGraphViewのサイズを設定 this.StretchToParentSize(); // MMBスクロールでズームインアウトができるように SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); // MMBドラッグで描画範囲を動かせるように this.AddManipulator(new ContentDragger()); // LMBドラッグで選択した要素を動かせるように this.AddManipulator(new SelectionDragger()); // LMBドラッグで範囲選択ができるように this.AddManipulator(new RectangleSelector()); } } ウィンドウに表示する

さてそれではこれをウィンドウに表示してみます。 これは普通にEditorWindowを作って前節のGraphViewをrootVisualElementとして追加するだけです。

using UnityEditor; public class ExampleGraphEditorWindow : EditorWindow { [MenuItem("Window/ExampleGraphEditorWindow")] public static void Open() { GetWindow(ObjectNames.NicifyVariableName(nameof(ExampleGraphEditorWindow))); } void OnEnable() { var graphView = new ExampleGraphView(this); rootVisualElement.Add(graphView); } }

このウィンドウを開くと以下のようにノードが表示されます。 この時点でマウス操作も効くようになっています。

f:id:halya_11:20200613222108p:plainウィンドウ

他のノードを作る

さてノードの表示が確認できたところで今回必要なノードをいくつか作っていきます。

まずは一番簡単そうなOutputノードです。

f:id:halya_11:20200613223925p:plainOutputノード

これはInputノードを一つ生成するだけです。

using UnityEditor.Experimental.GraphView; public class OutputNode : Node { public OutputNode() { title = "Output"; var port = Port.Create(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(float)); port.portName = "Value"; inputContainer.Add(port); } }

次にAddノードを作ります。

f:id:halya_11:20200613224044p:plainAddノード

二つのInputノードと一つのOutputノードを定義します。

using UnityEditor.Experimental.GraphView; public class AddNode : Node { public AddNode() { title = "Add"; var inputPort1 = Port.Create(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi, typeof(float)); inputPort1.portName = "A"; inputContainer.Add(inputPort1); var inputPort2 = Port.Create(Orientation.Horizontal, Direction.Input, Port.Capacity.Multi, typeof(float)); inputPort2.portName = "B"; inputContainer.Add(inputPort2); var outputPort = Port.Create(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, typeof(float)); outputPort.portName = "Out"; outputContainer.Add(outputPort); } }

最後に値を設定できるフィールドを持つValueノードです。

f:id:halya_11:20200613224331p:plainValueノード

ポートの設定方法は他のノードを変わりませんが、 値を設定するためのフィールドを追加するためにextensionContainerにFloatFieldを追加している点に注目してください。 またextensionContainerにUI要素を追加した場合にはRefreshExpandedState()を呼んで見た目に即時反映する必要があります。

using UnityEditor.Experimental.GraphView; using UnityEditor.UIElements; public class ValueNode : Node { public ValueNode() { title = "Value"; var port = Port.Create(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, typeof(float)); port.portName = "Value"; outputContainer.Add(port); extensionContainer.Add(new FloatField()); RefreshExpandedState(); } } ノードを右クリックメニューから作れるようにする

さて次にこれらのノードをグラフの右クリックメニューから作れるようにします。 右クリックメニューを作るにはISearchWindowProviderを実装したScriptableObjectを作成します。

using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEditor.Experimental.GraphView; using UnityEngine.UIElements; public class SearchMenuWindowProvider : ScriptableObject, ISearchWindowProvider { private ExampleGraphView _graphView; private EditorWindow _editorWindow; public void Initialize(ExampleGraphView graphView, EditorWindow editorWindow) { _graphView = graphView; _editorWindow = editorWindow; } List ISearchWindowProvider.CreateSearchTree(SearchWindowContext context) { var entries = new List(); entries.Add(new SearchTreeGroupEntry(new GUIContent("Create Node"))); // Exampleというグループを追加 entries.Add(new SearchTreeGroupEntry(new GUIContent("Example")) { level = 1 }); // Exampleグループの下に各ノードを作るためのメニューを追加 entries.Add(new SearchTreeEntry(new GUIContent(nameof(ValueNode))) { level = 2, userData = typeof(ValueNode) }); entries.Add(new SearchTreeEntry(new GUIContent(nameof(AddNode))) { level = 2, userData = typeof(AddNode) }); entries.Add(new SearchTreeEntry(new GUIContent(nameof(OutputNode))) { level = 2, userData = typeof(OutputNode) }); return entries; } bool ISearchWindowProvider.OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context) { var type = searchTreeEntry.userData as Type; var node = Activator.CreateInstance(type) as Node; // マウスの位置にノードを追加 var worldMousePosition = _editorWindow.rootVisualElement.ChangeCoordinatesTo(_editorWindow.rootVisualElement.parent, context.screenMousePosition - _editorWindow.position.position); var localMousePosition = _graphView.contentViewContainer.WorldToLocal(worldMousePosition); node.SetPosition(new Rect(localMousePosition, new Vector2(100, 100))); _graphView.AddElement(node); return true; } }

次に、右クリックをした時にこのメニューが開くようにGraphViewに処理を追加します。

using UnityEditor; using UnityEngine.UIElements; using UnityEditor.Experimental.GraphView; using UnityEngine; public class ExampleGraphView : GraphView { public ExampleGraphView(EditorWindow editorWindow) { AddElement(new ValueNode()); this.StretchToParentSize(); SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); this.AddManipulator(new ContentDragger()); this.AddManipulator(new SelectionDragger()); this.AddManipulator(new RectangleSelector()); // 右クリックメニューを追加 var menuWindowProvider = ScriptableObject.CreateInstance(); menuWindowProvider.Initialize(this, editorWindow); nodeCreationRequest += context => { SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), menuWindowProvider); }; } }

これでウィンドウの右クリックメニューからノードを作れるようになりました。

f:id:halya_11:20200613225425g:plain

ノード同士を繋げられるようにする

さてこのままではノードを繋げられないので繋げられるようにします。 これはGraphView.GetCompatiblePorts()をオーバーライドすることで行います。

対象のポートに接続可能なポートのリストを返却します。

using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine.UIElements; using UnityEditor.Experimental.GraphView; using UnityEngine; public class ExampleGraphView : GraphView { public ExampleGraphView(EditorWindow editorWindow) { AddElement(new ValueNode()); this.StretchToParentSize(); SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); this.AddManipulator(new ContentDragger()); this.AddManipulator(new SelectionDragger()); this.AddManipulator(new RectangleSelector()); var menuWindowProvider = ScriptableObject.CreateInstance(); menuWindowProvider.Initialize(this, editorWindow); nodeCreationRequest += context => { SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), menuWindowProvider); }; } // GetCompatiblePortsをオーバーライドする public override List GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) { var compatiblePorts = new List(); compatiblePorts.AddRange(ports.ToList().Where(port => { // 同じノードには繋げない if (startPort.node == port.node) return false; // Input同士、Output同士は繋げない if (port.direction == startPort.direction) return false; // ポートの型が一致していない場合は繋げない if (port.portType != startPort.portType) return false; return true; })); return compatiblePorts; } }

これでポート同士を接続できるようになりました。

f:id:halya_11:20200613230815g:plainポート同士を接続

関連

light11.hatenadiary.com

halya_11 2020-06-16 20:07 読者になる

この記事をはてなブックマークに追加 もっと読む コメントを書く


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3